package it.koutifaris.travianmap.ui;

import it.koutifaris.travianmap.map.WorldMap;
import it.koutifaris.travianmap.map.emptymap.EmptyWorldMap;
import it.koutifaris.travianmap.model.Village;
import it.koutifaris.travianmap.ui.PointToSquareHelper.SquareSizeSource;
import it.koutifaris.travianmap.ui.WorldMapViewInizializer.StepFinal;
import it.koutifaris.travianmap.ui.WorldMapViewInizializer.StepInitialState;
import it.koutifaris.travianmap.ui.WorldMapViewInizializer.StepMap;
import it.koutifaris.travianmap.ui.WorldMapViewInizializer.StepTheme;
import it.koutifaris.travianmap.ui.animation.Scheduler;
import it.koutifaris.travianmap.ui.drawhelpers.coords.CoordinatesDrawer;
import it.koutifaris.travianmap.ui.drawhelpers.coords.CoordinatesDrawerImpl;
import it.koutifaris.travianmap.ui.drawhelpers.village.HighDetailsVillageDrawer;
import it.koutifaris.travianmap.ui.drawhelpers.village.LowDetailsVillageDrawer;
import it.koutifaris.travianmap.ui.drawhelpers.village.VillageDrawer;
import it.koutifaris.travianmap.ui.drawhelpers.village.VillageDrawerConfig;
import it.koutifaris.travianmap.ui.drawhelpers.village.VillageDrawerFactory;
import it.koutifaris.travianmap.ui.theme.DefaultTheme;
import it.koutifaris.travianmap.ui.theme.WorldMapTheme;
import it.koutifaris.travianmap.ui.touch.MapTouchHandler;
import it.koutifaris.travianmap.ui.touch.MapViewOnTouchListener;
import it.koutifaris.travianmap.ui.touch.WolrdMapOnGestureListener;
import it.koutifaris.travianmap.ui.viewstate.DynamicViewState;
import it.koutifaris.travianmap.ui.viewstate.ViewStateBuilder;
import it.koutifaris.travianmap.ui.viewstate.ViewStateSource;
import it.koutifaris.travianmap.utils.ModularBounds;
import it.koutifaris.travianmap.utils.ModularBounds.BoundsSource;

import java.util.Iterator;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.ScaleGestureDetector;
import android.view.View;

public class WorldMapView extends View implements DynamicViewState.ViewStateListener, PointToVillageHelper {

	private WorldMap map;
	private DynamicViewState viewState;
	private ViewStateSource viewStateSource;
	private SquaredViewportBounds squaredViewportBounds;
	private WorldMapTheme theme;
	private PointToSquareHelper pointToSquareHelper;
	private ModularBounds modularBounds;
	private ImageFactory imageFactory;
	private VillageDrawerFactory villageDrawerFactory;
	private CoordinatesDrawer coordinatesDrawer;
	private int largestCoordinateHeight;

	public WorldMapView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		init(context);
	}

	public WorldMapView(Context context, AttributeSet attrs) {
		super(context, attrs);
		init(context);
	}

	public WorldMapView(Context context) {
		super(context);
		init(context);
	}

	private final void init(Context context) {
		largestCoordinateHeight = -1;
		defaultInitialization();

		setupPointToSquareHelper();
		setupModularBounds();
		setupImageFactory();
		setupViewPortBounds();
		setupGestures(context);

		setupVillageDrawerFactory();
		setupCoordinatesDrawer();
	}

	private void defaultInitialization() {
		initializer().startInitialing()
				.withMap(new EmptyWorldMap(WorldMap.TRAVIAN_MAP_RADIUS))
				.withDefaultTheme()
				.withDefaultInitialState()
				.done();
	}

	private void setupCoordinatesDrawer() {
		coordinatesDrawer = new CoordinatesDrawerImpl(pointToSquareHelper, modularBounds);
	}

	private void setupViewPortBounds() {
		squaredViewportBounds = new SquaredViewportBounds(getViewStateSource(), pointToSquareHelper);
	}

	private void setupImageFactory() {
		imageFactory = new ImageFactory(getResources());
	}

	private void setupPointToSquareHelper() {
		pointToSquareHelper = new PointToSquareHelper(new SquareSizeSource() {

			@Override
			public int getSquareSize() {
				return viewState.getSquareSize();
			}
		});
	}

	private void setupModularBounds() {
		modularBounds = new ModularBounds(new BoundsSource() {

			@Override
			public int getMin() {
				return -map.getRadius();
			}

			@Override
			public int getMax() {
				return +map.getRadius();
			}
		});
	}

	private void setupVillageDrawerFactory() {
		villageDrawerFactory = new VillageDrawerFactory(new VillageDrawerConfig() {

			@Override
			public VillageDrawer getDrawerFor(MapDetailsLevel detailsLevel) {
				switch (detailsLevel) {
				case HIGH:
					return new HighDetailsVillageDrawer(pointToSquareHelper, imageFactory);
				case LOW:
					return new LowDetailsVillageDrawer(pointToSquareHelper);
				}

				return null;
			}
		});
	}

	private void setupGestures(Context context) {
		WolrdMapOnGestureListener gestureListener = new WolrdMapOnGestureListener(
				getViewStateSource(),
				createViewPoster(),
				new MapTouchHandler(getViewStateSource(), new MapSourceImpl()),
				this);

		final GestureDetector gestureDetector = new GestureDetector(context, gestureListener);
		final ScaleGestureDetector scaleDetector = new ScaleGestureDetector(context, gestureListener);

		setOnTouchListener(new MapViewOnTouchListener(gestureDetector, scaleDetector));
	}

	public ViewStateSource getViewStateSource() {
		if (viewStateSource == null) {
			viewStateSource = createViewStateSource();
		}

		return viewStateSource;
	}

	private ViewStateSource createViewStateSource() {
		ViewStateSource viewStateSource = new ViewStateSource() {

			@Override
			public DynamicViewState getViewState() {
				return viewState;
			}
		};

		return viewStateSource;
	}

	private Scheduler createViewPoster() {
		return new Scheduler() {

			@Override
			public void scheduleDelayed(Runnable action, long delayMillis) {
				WorldMapView.this.postDelayed(action, delayMillis);
			}

			@Override
			public void scheduleNow(Runnable action) {
				WorldMapView.this.post(action);
			}
		};
	}

	public WorldMapViewInizializer initializer() {
		return new Initializer();
	}

	@Override
	public Village getVillageFromPoint(int x, int y) {
		int squareX = modularBounds.module(pointToSquareHelper.getSquare(viewState.getX() + x));
		int squareY = modularBounds.module(pointToSquareHelper.getSquare(viewState.getY() + y));

		Log.d(VIEW_LOG_TAG, "Tapped coords: (x: " + squareX + ", y: " + squareY + ")");

		return map.getSquareAt(squareX, squareY).getVillage();
	}

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);

		drawBackGround(canvas);
		drawMapArea(canvas);
		drawCoordinates(canvas);
	}

	private void drawBackGround(Canvas canvas) {
		canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), theme.getMapBackGround());
	}

	private void drawMapArea(Canvas canvas) {
		setMapClipArea(canvas);
		drawVillages(canvas);
		drawColumns(canvas);
		drawRows(canvas);
		drawSelection(canvas);
		restoreClipArea(canvas);
	}

	private void restoreClipArea(Canvas canvas) {
		canvas.restore();
	}

	private void setMapClipArea(Canvas canvas) {
		canvas.save();
		int textMaxWidth = Math.round(theme.getCoordsText().measureText("0"));
		canvas.clipRect(textMaxWidth, theme.getCoordsText().getTextSize(), canvas.getWidth(), canvas.getHeight());
	}

	private void drawVillages(Canvas canvas) {
		VillageDrawer villageDrawer = chooseVillageDrawer();
		villageDrawer.setup(canvas, viewState);

		for (Iterator<Integer> xIt = squaredViewportBounds.xIterator(canvas.getWidth()); xIt.hasNext();) {
			int x = xIt.next();
			for (Iterator<Integer> yIt = squaredViewportBounds.yIterator(canvas.getHeight()); yIt.hasNext();) {
				int y = yIt.next();
				int mx = modularBounds.module(x);
				int my = modularBounds.module(y);

				Village village = map.getSquareAt(mx, my).getVillage();
				if (village != null) {
					villageDrawer.drawVillage(village, x, y);
				}
			}
		}
	}

	private VillageDrawer chooseVillageDrawer() {
		return villageDrawerFactory.getVillageDrawer(viewState.getDetailsLevel());
	}

	private void drawColumns(Canvas canvas) {
		for (Iterator<Integer> xIt = squaredViewportBounds.xIterator(canvas.getWidth()); xIt.hasNext();) {
			Integer x = xIt.next();
			int colX = pointToSquareHelper.getSquareCorner(x) - viewState.getX();
			Paint paint = (isMapXBorder(modularBounds.module(x))) ? theme.getMapBorder() : theme.getBorder();
			canvas.drawLine(colX, 0, colX, canvas.getHeight(), paint);
		}
	}

	private boolean isMapXBorder(int mx) {
		return (mx == 0) || (Math.abs(mx) == map.getRadius());
	}

	private void drawRows(Canvas canvas) {
		for (Iterator<Integer> yIt = squaredViewportBounds.yIterator(canvas.getHeight()); yIt.hasNext();) {
			Integer y = yIt.next();
			int rowY = pointToSquareHelper.getSquareCorner(y) - viewState.getY();
			Paint paint = (isMapYBorder(modularBounds.module(y))) ? theme.getMapBorder() : theme.getBorder();
			canvas.drawLine(0, rowY, canvas.getWidth(), rowY, paint);
		}
	}

	private boolean isMapYBorder(int my) {
		return my == 0 || Math.abs(my) == map.getRadius();
	}

	private void drawCoordinates(Canvas canvas) {
		coordinatesDrawer.setup(canvas, viewState, getCoordinateRate());

		coordinatesDrawer.drawXCoordinates(squaredViewportBounds.xIterator(canvas.getWidth()), theme.getCoordsText());
		coordinatesDrawer.drawYCoordinates(squaredViewportBounds.yIterator(canvas.getHeight()), theme.getCoordsText());
	}

	private int getCoordinateRate() {
		return (viewState.getSquareSize() > getLargestCoordinateHeight()) ? 1 : 5;
	}

	private int getLargestCoordinateHeight() {
		if (largestCoordinateHeight < 0) {
			String largestCoordinate = String.valueOf(-map.getRadius());
			char[] chars = largestCoordinate.toCharArray();
			largestCoordinateHeight = Math.round(theme.getCoordsText().getTextSize() * chars.length);
		}

		return largestCoordinateHeight;
	}

	private void drawSelection(Canvas canvas) {
		if (viewState.getSelectedSquare() != null) {
			int x = viewState.getSelectedSquare().getX();
			int y = viewState.getSelectedSquare().getY();
			int colX = pointToSquareHelper.getSquareCorner(x) - viewState.getX();
			int rowY = pointToSquareHelper.getSquareCorner(y) - viewState.getY();
			int size = viewState.getSquareSize();

			canvas.drawRect(colX, rowY, colX + size, rowY + size, theme.getSelectionFill());
			canvas.drawRect(colX - 1, rowY - 1, colX + size + 1, rowY + size + 1, theme.getSelection());
		}
	}

	private void setMap(final WorldMap map) {
		this.map = map;
	}

	private void setTheme(WorldMapTheme theme) {
		this.theme = theme;
	}

	private void setViewState(DynamicViewState viewState) {
		this.viewState = viewState;
		this.viewState.setListener(this);
	}

	@Override
	public void onViewStateChanged() {
		this.invalidate();
	}

	private class Initializer implements WorldMapViewInizializer, StepMap, StepTheme, StepInitialState, StepFinal {

		@Override
		public StepMap startInitialing() {
			return this;
		}

		@Override
		public StepTheme withMap(WorldMap map) {
			setMap(map);
			return this;
		}

		@Override
		public StepInitialState withTheme(WorldMapTheme theme) {
			setTheme(theme);
			return this;
		}

		@Override
		public StepInitialState withDefaultTheme() {
			setTheme(new DefaultTheme());
			return this;
		}

		@Override
		public StepFinal withInitialState(DynamicViewState viewState) {
			setViewState(viewState);
			return this;
		}

		@Override
		public StepFinal withDefaultInitialState() {
			setViewState(ViewStateBuilder.dafaultViewState());
			return this;
		}

		@Override
		public WorldMapView done() {
			return WorldMapView.this;
		}

	}

	private class MapSourceImpl implements MapSource {

		@Override
		public WorldMap getMap() {
			return map;
		}

	}

}
